package com.qubi.exchange.service;


import com.qubi.exchange.biz.ColaExchangeBiz;
import com.qubi.exchange.data.BalanceChange;
import com.qubi.exchange.entity.Balance;
import com.qubi.exchange.mapper.BalanceMapper;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class BalanceService implements ApplicationRunner {
    @Autowired
    BalanceMapper balanceMapper;
    @Autowired
    ColaExchangeBiz exchangeBiz;

    static Map<String, Map<String, Balance>> balanceCache = new ConcurrentHashMap<>(400);
    static Map<String, String> balanceCacheLocker = new ConcurrentHashMap<>(400);


    public boolean changeUserBalanceCache(String userId, String coinCode, BigDecimal available, BigDecimal frozen, BigDecimal lock) {
        var userBalanceMap = balanceCache.get(userId);
        Balance userBalance = null;
        if (userBalanceMap != null) {
            userBalance = userBalanceMap.get(coinCode);
        }
        if (userBalanceMap == null || userBalance == null) {
            userBalance = initBalanceByCoinCode(userId, coinCode);
            userBalanceMap = balanceCache.get(userId);

        }
        boolean result = userBalance.changeBalance(available, frozen, lock);
        if (!result) {
            userBalance = balanceMapper.selectByPrimaryKey(userBalance.getId());
            userBalanceMap.put(coinCode, userBalance);
            result = userBalance.changeBalance(available, frozen, lock);

        }
        return result;
    }

    @Transactional
    public boolean changeUserBalance(String userId, String coinCode, BigDecimal available, BigDecimal frozen, BigDecimal lock)
    {
        if(available==null) available=BigDecimal.ZERO;
        if(frozen==null) frozen=BigDecimal.ZERO;
        if(lock==null) lock=BigDecimal.ZERO;
        int  index=0;
        if(changeUserBalanceCache(userId,coinCode,available,frozen,lock)){
            BalanceChange balanceChange=new BalanceChange();
            balanceChange.setId(userId+coinCode);
            balanceChange.setUserId(userId);
            balanceChange.setAvailable(available);
            balanceChange.setFrozen(frozen);
            balanceChange.setLocks(lock);
            List<BalanceChange>balanceChanges=new ArrayList<>(1);
            balanceChanges.add(balanceChange);
              index= balanceMapper.batchUpdateAll(balanceChanges);
        }
        return index>0;

    }


    public Balance getUserBalance(String userId, String coinCode) {
        return balanceMapper.selectByPrimaryKey(userId + coinCode);
    }


    public Map<String,BigDecimal>getCoinAllBalance(String coinCode)
    {
        Example example = new Example(Balance.class);
        example.createCriteria().andEqualTo("coinCode",coinCode).andGreaterThan("locks",BigDecimal.ZERO);
       var list= balanceMapper.selectByExample(example);
        Map<String,BigDecimal>map=new HashMap<>((int)(list.size()*1.4+1));
        for(Balance balance:list){

            map.put(balance.getUserId(),balance.getLocks().setScale(2,RoundingMode.DOWN));

        }
        return map;
    }

    @Transactional
    public Balance initBalanceByCoinCode(String userId, String coinCode) {

        var userMap = balanceCache.getOrDefault(userId, new ConcurrentHashMap<>());
        Balance balance = new Balance();
        balance.setId(userId + coinCode);
        balance.setUserId(userId);
        balance.setCoinCode(coinCode);
        balance.setBalanceAvailable(BigDecimal.ZERO);
        balance.setBalanceFrozen(BigDecimal.ZERO);
        balance.setLocks(BigDecimal.ZERO);
        try {
            balanceMapper.insert(balance);
        } catch (Throwable e) {
            e.printStackTrace();
            balance = balanceMapper.selectByPrimaryKey(userId + coinCode);
        }
        userMap.put(coinCode, balance);
        balanceCache.put(userId, userMap);
        return balance;
    }

    public long initBalance() {

        Example example = new Example(Balance.class);
        example.orderBy("id").desc();
        long total = 0;
        int page = 1;
        while (true) {
            List<Balance> balanceList = balanceMapper.selectByExampleAndRowBounds(example, new RowBounds(5000 * (page - 1), 5000));
            for (Balance balance : balanceList) {
                var map = this.balanceCache.getOrDefault(balance.getUserId(), new ConcurrentHashMap<>());
                map.putIfAbsent(balance.getCoinCode(), balance);
                this.balanceCache.putIfAbsent(balance.getUserId(), map);
            }
            total += balanceList.size();
            if (balanceList.size() < 5000) return total;
        }


    }

    public Map<String, BigDecimal> getAllUserWorth() {
        List<Balance> balanceList = balanceMapper.selectAll();
        Map<String, BigDecimal> bigDecimalMap = new HashMap<>(400);

        balanceList.forEach(x -> {
            BigDecimal worth = bigDecimalMap.getOrDefault(x.getUserId(), BigDecimal.ZERO);
            var coinCode = x.getCoinCode();
            var amout = x.getBalanceAvailable().add(x.getBalanceFrozen()).add(x.getLocks());
            var coinPrice = exchangeBiz.getCoinPrice(coinCode);
            if (coinPrice == null) coinPrice = BigDecimal.ZERO;

            worth = worth.add(amout.multiply(coinPrice)).setScale(2, RoundingMode.DOWN);

            bigDecimalMap.put(x.getUserId(), worth);


        });

        return bigDecimalMap;

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initBalance();
        System.out.println("初始化币币账户余额完成");
    }


}
